﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Reflection;
using System.IO;
using PSE.Hosting.ServiceHoster.Logging;
using System.Security.Policy;
using PSE.Hosting.ServiceHoster.Configuration;

namespace PSE.Hosting.ServiceHoster.Business
{
    [Serializable]
    internal class DomainController : IDisposable
    {
        private string _packageName = null;
        private AppDomain _appDomain = null;
        private string _webConfigFile = null;
        private string _shadowBaseDirectory = null;
        private string _shadowTempBaseDirectory = null;

        private static object _lock = new object();
        private static DomainController _instance;

        public AppDomain ServiceDomain
        {
            get { return _appDomain; }
        }

        public string ShadowBaseDirectory
        {
            get { return _shadowBaseDirectory; }
        }

        private DomainController()
        {
        }

        public static DomainController GetInstance()
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new DomainController();
            }
            return _instance;
        }

        public void Initialize(string packageName)
        {
            _packageName = packageName;

            //_shadowBaseDirectory = string.Format(@"{0}\{1}\{2}", Environment.GetEnvironmentVariable(Constants.TempVariable), Constants.ServiceHosterFolder, _packageName);

            _shadowTempBaseDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Constants.TempServiceHosterFolder);
            _shadowBaseDirectory = Path.Combine(_shadowTempBaseDirectory, _packageName);

            OpenServiceDomain(_shadowBaseDirectory);
        }

        public void OpenServiceDomain(string baseDirectory)
        {
            if (_appDomain == null)
            {
                _webConfigFile = Path.Combine(baseDirectory, "web.config");

                CreateConfigurationFile();

                AppDomainSetup info = new AppDomainSetup();
                info.ConfigurationFile = _webConfigFile;

                Evidence adevidence = AppDomain.CurrentDomain.Evidence;

                _appDomain = AppDomain.CreateDomain(_packageName, adevidence, info);
                _appDomain.AssemblyResolve += new ResolveEventHandler(_appDomain_AssemblyResolve);
            }
        }

        public void CloseServiceDomain()
        {
            if (_appDomain != null)
            {
                _appDomain.AssemblyResolve -= new ResolveEventHandler(_appDomain_AssemblyResolve);

                try
                {
                    AppDomain.Unload(_appDomain);

                    DeleteConfigurationFile();

                    DeleteConfigurationFolder();
                }
                catch (CannotUnloadAppDomainException ex)
                {
                    Logger.Log(ex, Logger.EntryType.Error);
                }

                _appDomain = null;
            }
        }

        private void CreateConfigurationFile()
        {
            if (!Directory.Exists(_shadowBaseDirectory))
                Directory.CreateDirectory(_shadowBaseDirectory);

            if (!Directory.Exists(Path.GetDirectoryName(_webConfigFile)))
                Directory.CreateDirectory(Path.GetDirectoryName(_webConfigFile));

            if (!File.Exists(_webConfigFile))
                File.WriteAllText(_webConfigFile, "<configuration></configuration>");
        }

        public void DeleteConfigurationFile()
        {
            try
            {
                if (File.Exists(_webConfigFile))
                {
                    File.Delete(_webConfigFile);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex, Logger.EntryType.Error);
            }
        }

        public void DeleteConfigurationFolder()
        {
            if (Directory.Exists(_shadowBaseDirectory))
            {
                Directory.Delete(_shadowBaseDirectory, true);
            }

            if (Directory.GetFileSystemEntries(_shadowTempBaseDirectory).Length == 0)
            {
                Directory.Delete(_shadowTempBaseDirectory, true);
            }
        }

        public void MergeConfigurationFile(string configurationFile)
        {
            try
            {
                if (File.Exists(configurationFile))
                {
                    CreateConfigurationFile();

                    XmlDocument webConfig = new XmlDocument();
                    webConfig.Load(_webConfigFile);

                    // Abrindo arquivo config
                    XmlDocument configFile = new XmlDocument();
                    configFile.Load(configurationFile);

                    if (webConfig.SelectSingleNode("/configuration") == null &&
                        configFile.SelectSingleNode("/configuration") != null)
                    {
                        AppendChild(webConfig, configFile.SelectSingleNode("/configuration"));
                    }
                    else
                    {
                        foreach (XmlNode element in configFile.SelectNodes("/configuration/*"))
                        {
                            #region configuration
                            // Percorre todas as tag do config e adicina no web.config se
                            // a tag não existir
                            if (webConfig.SelectSingleNode("/configuration/" + element.Name) == null)
                            {
                                AppendChild(webConfig.SelectSingleNode("/configuration"), element);
                            }
                            #endregion

                            #region configSections
                            // Se a tag existir faz merge dos elementos
                            else if (element.Name == "configSections")
                            {
                                foreach (XmlNode section in element.ChildNodes)
                                {
                                    if (section.Name == "section" && section.Attributes != null && section.Attributes["name"] != null &&
                                        webConfig.SelectSingleNode("/configuration/configSections/section[@name='" + section.Attributes["name"].Value + "']") == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration/configSections"), section);
                                    }
                                }
                            }
                            #endregion

                            #region appSettings
                            else if (element.Name == "appSettings")
                            {
                                foreach (XmlNode setting in element.ChildNodes)
                                {
                                    if (setting.Name == "add" && setting.Attributes != null && setting.Attributes["key"] != null &&
                                        webConfig.SelectSingleNode("/configuration/appSettings/add[@key='" + setting.Attributes["key"].Value + "']") == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration/appSettings"), setting);
                                    }
                                }

                            }
                            #endregion

                            #region system.serviceModel
                            else if (element.Name == "system.serviceModel")
                            {
                                #region extensions
                                // extensions
                                XmlNode extensions = element.SelectSingleNode("extensions");

                                if (extensions != null)
                                {
                                    if (webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions") == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), extensions);
                                    }
                                    else
                                    {
                                        // behaviorExtensions
                                        XmlNode behaviorExtensions = extensions.SelectSingleNode("behaviorExtensions");

                                        if (behaviorExtensions != null)
                                        {
                                            if (webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions/behaviorExtensions") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions"), behaviorExtensions);
                                            }
                                            else
                                            {
                                                foreach (XmlNode behaviorExtension in behaviorExtensions.ChildNodes)
                                                {
                                                    if (behaviorExtension.Name == "behavior" && behaviorExtension.Attributes != null && behaviorExtension.Attributes["name"] != null &&
                                                        webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions/behaviorExtensions/add[@name='" + behaviorExtension.Attributes["name"].Value + "']") == null)
                                                    {
                                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions/behaviorExtensions"), behaviorExtension);
                                                    }
                                                }
                                            }
                                        }

                                        // bindingExtensions
                                        XmlNode bindingExtensions = extensions.SelectSingleNode("behaviorExtensions");

                                        if (bindingExtensions != null)
                                        {
                                            if (webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions/bindingExtensions") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions"), bindingExtensions);
                                            }
                                            else
                                            {
                                                foreach (XmlNode bindingExtension in bindingExtensions.ChildNodes)
                                                {
                                                    if (bindingExtension.Name == "behavior" && bindingExtension.Attributes != null && bindingExtension.Attributes["name"] != null &&
                                                        webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions/bindingExtensions/add[@name='" + bindingExtension.Attributes["name"].Value + "']") == null)
                                                    {
                                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/extensions/bindingExtensions"), bindingExtension);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                #endregion


                                #region Behaviors
                                // Behaviors
                                XmlNode behaviors = element.SelectSingleNode("behaviors");

                                if (behaviors != null)
                                {
                                    if (webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors") == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), behaviors);
                                    }
                                    else
                                    {
                                        // Service Behaviors
                                        XmlNode serviceBehaviors = behaviors.SelectSingleNode("serviceBehaviors");

                                        if (serviceBehaviors != null)
                                        {
                                            if (webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors/serviceBehaviors") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors"), serviceBehaviors);
                                            }
                                            else
                                            {
                                                foreach (XmlNode behavior in serviceBehaviors.ChildNodes)
                                                {
                                                    if (behavior.Name == "behavior" && behavior.Attributes != null && behavior.Attributes["name"] != null &&
                                                        webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors/serviceBehaviors/behavior[@name='" + behavior.Attributes["name"].Value + "']") == null)
                                                    {
                                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors/serviceBehaviors"), behavior);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                #endregion

                                #region Bindings
                                // Bindings
                                XmlNode bindings = element.SelectSingleNode("bindings");

                                if (bindings != null)
                                {
                                    // Verifica se a tag bindings já existe no arquivo, se não existir adiciona a tag completa
                                    if (webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings") == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), bindings);
                                    }
                                    else
                                    {
                                        // Se já existir, faz merge dos bindings
                                        foreach (XmlNode customBinding in bindings.ChildNodes)
                                        {
                                            if (customBinding.NodeType != XmlNodeType.Comment)
                                            {
                                                if (webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings/" + customBinding.Name) == null)
                                                {
                                                    AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings"), customBinding);
                                                }
                                                else
                                                {
                                                    foreach (XmlNode binding in customBinding.ChildNodes)
                                                    {
                                                        if (binding.Name == "binding" && binding.Attributes != null && binding.Attributes["name"] != null &&
                                                            webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings/" + customBinding.Name + "/binding[@name='" + binding.Attributes["name"].Value + "']") == null)
                                                        {
                                                            AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings/" + customBinding.Name), binding);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                #endregion

                                #region Services
                                // Services
                                XmlNode services = element.SelectSingleNode("services");

                                if (services != null)
                                {
                                    // Verifica se a tag services já existe no arquivo, se não existir adiciona a tag completa
                                    if (webConfig.SelectSingleNode("/configuration/system.serviceModel/services") == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), services);
                                    }
                                    else
                                    {
                                        foreach (XmlNode service in services.ChildNodes)
                                        {
                                            if (service.Name == "service" && service.Attributes != null && service.Attributes["name"] != null &&
                                                webConfig.SelectSingleNode("/configuration/system.serviceModel/services/service[@name='" + service.Attributes["name"].Value + "']") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/services"), service);
                                            }
                                        }
                                    }
                                }
                                #endregion
                            }
                            #endregion
                        }
                    }

                    webConfig.Save(_webConfigFile);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex, Logger.EntryType.Error);
            }
        }

        private void AppendChild(XmlNode parentNode, XmlNode childNode)
        {
            XmlDocument document = parentNode as XmlDocument ?? parentNode.OwnerDocument;

            XmlNode newNode = document.CreateElement(childNode.Name);
            newNode.InnerXml = childNode.InnerXml;

            if (childNode.Attributes != null)
            {
                foreach (XmlAttribute att in childNode.Attributes)
                {
                    XmlAttribute newAttribute = document.CreateAttribute(att.Name);
                    newAttribute.Value = att.Value;

                    newNode.Attributes.Append(newAttribute);
                }
            }

            parentNode.AppendChild(newNode);
        }

        private Assembly _appDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly assembly = null;
            string _shadowCodeBaseDirectory;

            try
            {
                AppDomain appDomain = (AppDomain)sender;

                List<string> folderList = (List<string>)appDomain.GetData("FolderList");

                string assemblyName;
                if (args.Name.IndexOf(",") == -1)
                    assemblyName = args.Name + ".dll";
                else
                    assemblyName = args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";

                _shadowCodeBaseDirectory = (string)appDomain.GetData("ShadowCodeBaseDirectory");

                if (folderList != null)
                {
                    foreach (string folder in folderList)
                    {
                        if (Directory.Exists(folder))
                        {
                            string[] files = Directory.GetFiles(folder, assemblyName);

                            foreach (string file in files)
                            {
                                string _shadowCodeBaseFile = Path.Combine(_shadowCodeBaseDirectory, Path.GetFileName(file));

                                _shadowCodeBaseFile = VerifyFile(_shadowCodeBaseFile);

                                if (!File.Exists(_shadowCodeBaseFile))
                                {
                                    File.Copy(file, _shadowCodeBaseFile);

                                    File.SetAttributes(_shadowCodeBaseFile, FileAttributes.Normal);
                                }

                                assembly = Assembly.LoadFile(_shadowCodeBaseFile);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex, Logger.EntryType.Error);
            }

            return assembly;
        }

        private string VerifyFile(string _shadowCodeBaseFile)
        {
            string fileFound = string.Empty;

            foreach (string file in Directory.GetFiles(Path.GetDirectoryName(_shadowCodeBaseFile)))
            {
                if (file.Contains(Path.GetFileNameWithoutExtension(_shadowCodeBaseFile)))
                {
                    fileFound = file;
                }
            }

            if (!string.IsNullOrEmpty(fileFound))
            {
                List<FileInfo> files = new List<FileInfo>();

                foreach (string file in Directory.GetFiles(Path.GetDirectoryName(_shadowCodeBaseFile), Path.GetFileNameWithoutExtension(_shadowCodeBaseFile) + "*.dll"))
                {
                    files.Add(new FileInfo(file));
                }

                FileInfo lastWriteFile = files.OrderByDescending(f => f.LastWriteTime).First();

                if (lastWriteFile != null)
                {
                    fileFound = lastWriteFile.FullName;
                }
            }
            else
            {
                fileFound = _shadowCodeBaseFile;
            }

            return fileFound;
        }

        public void Dispose()
        {
            this.CloseServiceDomain();

            _packageName = null;
            _shadowBaseDirectory = null;
            _shadowTempBaseDirectory = null;
        }
    }
}
